Spring Boot应用容器化及Kubernetes部署

一个Spring Boot应用容器化及Kubernetes部署全记录。

Docker & Kubernetes

Docker是一种容器技术,它能将应用所需的所有内容打包成一个Docker镜像,其目标是使开发者能方便地创建、部署、运行应用。Docker很像虚拟技术,其依赖的基础镜像通常是超精简版的OS镜像,但最关键的一点就是能保证在不同的宿主机环境下容器运行效果的一致性。
Kubernetes则是一种容器编排平台,且不仅仅支持Docker容器,当然主流的用户还是使用Docker容器。Kubernetes的强大之处在于它能支持大量容器应用协同运行的同时减少运维负担,如能根据硬件资源的状况在宿主机集群上调度容器、根据用户需求自动快速扩缩容等。

项目

这是一个真实的项目,涵盖了大部分常见的Spring Boot使用方式。

  1. 出于脱敏的目的,项目名以Test替代。
  2. 项目的结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Test
    - src
    - main
    - java
    - resources
    - test
    - java
    - target
    - .gitignore
    - application.yml
    - Dockerfile
    - pom.xml
    - README.md
  3. 项目根据环境不同需要读取不同的application.yml

  4. 项目使用logback作为日志工具,需要读取一个logback-spring.xml的配置文件。
  5. 项目还需要读取一个properties文件。
  6. 为方便期间,所有Kubernetes资源均在default namespace下。
namespace

Kubernetes的许多资源对象都隶属于某个namespace,namespace是对资源的逻辑划分,不同namespace下的资源可以做到简单隔离。

准备工作

镜像

针对项目jar包,我们需要将其打成一个Docker镜像,而Dockerfile就是打包过程的指导文件,类似于make过程中的Makefile

jar->Docker镜像

以下是我们需要的Dockerfile内容。

1
vim Dockerfile

1
2
3
4
5
6
7
8
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/test.jar test.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar", \
"-Dspring.config.location=/etc/config/application.yml", \
"-Dlogging.config=/etc/config/logback-spring.xml", \
"-DconfPath=/etc/config", \
"/test.jar"]
  1. FROM表示要生产的镜像的基础镜像,我们使用jdk8。
  2. VOLUME表示定义一个匿名卷,也可以理解为创建一个目录,这里的/tmp是由于tomcat的需要。
  3. ADD表示将项目编译后的jar包拷贝到镜像里,默认为根目录/
  4. ENTRYPOINT表示入口点,即容器启动时执行的命令,通过上述组合,实际执行的命令为:java -Djava.security.egd=file:/dev/./urandom -jar -Dspring.config.location=/etc/config/application.yml -Dlogging.config=/etc/config/logback-spring.xml -DconfPath=/etc/config /test.jar
    注意这里的配置路径/etc/config还不存在,下文中会创建和用到。
配置
Kubernetes ConfigMap

ConfigMap可以用来保存单个的键值对,也可以保存配置文件。

配置文件->ConfigMap

针对项目配置,需要做的改造就是把配置文件(应用、日志、业务)转化成ConfigMap对象,从而让Kubernetes化后的应用能够读取。

1
vim test-config.yml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: default
data:
application-yml: |
... // 应用配置内容
logback-spring-xml: |
... // 日志配置内容
other-config: |
... // 业务配置内容

  1. 注意配置文件按照yaml规范缩进。
  2. data属性里记录实际的配置,本质上仍是键值对,如第一个数据的key为application-yml,内容为其配置内容。
应用部署定义
Kubernetes Deployment

Deployment是一种控制容器组的对象,在Deployment中定义一个期望的容器组的数量,Deployment在创建后会维持这个数量,当数量少于期望时会新建容器组,反之会停止超额的容器组。

部署->Deployment

针对本项目,对应的Deployment配置如下。

1
vim test-deployment.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: test-deployment
labels:
app: test-deployment
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: test
image: test:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 7777
volumeMounts:
- name: config-volume
mountPath: /etc/config
- name: log-volume
mountPath: /var/log/test
volumes:
- name: config-volume
configMap:
name: test-config
items:
- key: application-yml
path: application.yml
- key: logback-spring-xml
path: logback-spring.xml
- key: other-config
path: other.config
- name: log-volume
hostPath:
path: /var/log/test
  1. replicas即为此Deployment期望的容器组的数量。
  2. metadata.labels用于跟下文中的Service对接。
  3. spec.selector.matchLabels与容器组的spec.template.metadata.labels对接。
  4. spec.template.spec.volumes定义了一些卷,name为卷名。
    • 如果定义了configMap属性,则items属性的key对应上文中ConfigMap对象的名称,path则为要输出的文件,如第一个item的行为是读取ConfigMap对象test-config的第一个数据的内容(key=application-yml),并将其保存为application.yml配置文件。
    • 如果定义了hostPath属性,则是宿主机上的同名目录,如此处将容器日志输出到宿主机的/var/log/test路路径。
  5. spec.template.spec.containers定义了容器组中的容器,包括容器名、镜像、容器暴露的端口,以及要挂载的卷,在volumeMounts中,name为上述卷名,mountPath为挂载路径,从而容器内部存在/etc/config路径,且可以读取到上述配置文件。
服务定义
Kubernetes Service

默认情况下,Kubernetes集群内的容器是不能被集群外访问的。此时我们需要将应用连接到Service,Service可以对集群内部暴露,也可以对外暴露宿主机端口,还能通过LB VIP暴露服务。

外部访问->Service

针对本项目,对应的Service配置如下。

1
vim test-service.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: test-service
namespace: default
spec:
selector:
app: test-app
type: NodePort
ports:
- protocol: TCP
port: 7777
targetPort: 7777
nodePort: 30777
  1. spec.selector用于连接容器组,与上文中Deployment定义的容器组的labels对应。
  2. spec.type定义Service的暴露方式,NodePort为对集群外暴露,用户通过宿主机端口访问应用,即:
    • 用户->宿主机IP:30777->Service:7777->容器:7777

部署工作

编译jar包

不论使用Maven还是Gradle,生成jar包,此处不细说。

生成Docker镜像

使用以下命令生成Docker镜像,当然,前提是生成镜像的机器上需要有基础镜像openjdk:8-jdk-alpine

1
2
cd Test
docker build -t test:latest .

同时,需要让所有Kubernetes的Node能获取到该镜像,有两种途径:

  1. 将该镜像打包并在所有Node上加载。

    1
    2
    docker save -o test.tar test:latest
    docker load -i test.tar
  2. 将该镜像推送到所有Node均能访问的镜像仓库里。

创建Kubernetes资源

分别创建ConfigMap、Deployment、Service。

1
2
3
kubectl apply -f test-config.yml
kubectl apply -f test-deployment.yml
kubectl apply -f test-service.yml

验证

查看资源对象是否正常。

1
2
3
kubectl get po | grep test
kubectl get deploy | grep test
kubectl get svc | grep test

访问服务。

1
curl <宿主机IP>:30777